/*
 * Invoice.java
 * 
 * @version 1.0
 * CS 2340 Team 10: Cyberdyne Systems
 */

package edu.gatech.oad.cyberdoc.domain;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import edu.gatech.oad.cyberdoc.database.DatabaseSerializable;

/**
 * Stores the information about an invoice and provides method for manipulating
 * them.
 * 
 * @author John Girata
 * @version 1.0
 */
public class Invoice implements Comparable<Invoice>, DatabaseSerializable {
	/**
	 * The logger for this class.
	 */
	private static final Logger LOG;

	/**
	 * A map storing all instances of Invoice for quick look-up.
	 */
	private static final Map<Integer, Invoice> INVOICES;

	/**
	 * The Invoice ID number.
	 */
	private final int id;

	/**
	 * The patient associated with this invoice.
	 */
	private Patient patient;

	/**
	 * The doctor associated with this invoice.
	 * 
	 * TODO Abstract to multiple doctors/staff members
	 */
	private Doctor doctor;

	/**
	 * The date the services in this invoice were performed.
	 */
	private Date date;

	/**
	 * The line items in this invoice.
	 */
	private List<InvoiceItem> items;

	static {
		INVOICES = new HashMap<Integer, Invoice>();
		LOG = Logger.getLogger("edu.gatech.oad.cyberdoc.domain.Invoice");
	}

	/**
	 * Creates a new Invoice with all of the possible information supplied.
	 * 
	 * @param id
	 *            The Invoice ID number
	 * @param patient
	 *            The patient associated with this invoice
	 * @param date
	 *            The date the services in this invoice were performed
	 * @param doctor
	 *            The doctor associated with this invoice
	 * @param items
	 *            The line items in this invoice
	 */
	public Invoice(int id, Patient patient, Doctor doctor, Date date,
			List<InvoiceItem> items) {
		this.id = id;
		this.patient = patient;
		this.doctor = doctor;
		this.date = date;
		this.items = items;

		INVOICES.put(id, this);
		LOG.info("Invoice \"" + id + "\" created for \"" + patient + "\".");
	}

	/**
	 * Creates an instance of this class from a JSONObject, as required by
	 * DatabaseSerializable.
	 * 
	 * @param object
	 *            The information from the database to use to create this User
	 * @throws JSONException
	 *             If this constructor is given an improperly formatted
	 *             exception
	 */
	public Invoice(JSONObject object) throws JSONException {
		id = object.getInt("id");
		patient = new Patient(object.getJSONObject("patient"));
		doctor = new Doctor(object.getJSONObject("doctor"));

		try {
			date = new Date(Long.parseLong(object.getString("date")));
		} catch (NumberFormatException e) {
			LOG.log(Level.SEVERE, "Date has an incorrect format", e);
		}

		final JSONArray encodedItems = object.getJSONArray("items");
		items = new ArrayList<InvoiceItem>();

		for (int i = 0; i < encodedItems.length(); i++) {
			items.add(new InvoiceItem(encodedItems.getJSONObject(i)));
		}

		LOG.info("New Invoice [id=" + id + "] created");
		INVOICES.put(id, this);
	}

	/**
	 * Creates a new Invoice with no line items.
	 * 
	 * @param id
	 *            The Invoice ID number
	 * @param patient
	 *            The patient associated with this invoice
	 * @param date
	 *            The date the services in this invoice were performed
	 * @param doctor
	 *            The doctor associated with this invoice
	 */
	public Invoice(int id, Patient patient, Doctor doctor, Date date) {
		this(id, patient, doctor, date, new ArrayList<InvoiceItem>());
	}

	/**
	 * Adds a line item to this Invoice.
	 * 
	 * @param item
	 *            The line item to add
	 */
	public void addItem(InvoiceItem item) {
		items.add(item);

		LOG.info("Invoice item \"" + item + "\" added to invoice \"" + id
				+ "\"");
	}

	/**
	 * Retrieves a previously created Invoice given the Invoice's ID number.
	 * 
	 * @param id
	 *            The Invoice's ID number
	 * 
	 * @return The Invoice associated with the ID number
	 */
	public static Invoice getInvoice(Integer id) {
		return INVOICES.get(id);
	}

	/**
	 * Returns a sorted list of all invoices currently in the system.
	 * 
	 * @return All invoices
	 */
	public static List<Invoice> getAllInvoices() {
		final List<Invoice> invoices = new ArrayList<Invoice>(INVOICES.values());
		Collections.sort(invoices);

		return invoices;
	}

	/**
	 * Determines the total cost of this invoice.
	 * 
	 * @return The total cost of this invoice
	 */
	public double getTotal() {
		double total = 0;

		for (InvoiceItem item : items) {
			total += item.getCost();
		}

		return total;
	}

	/**
	 * Returns a string representation of this Invoice.
	 * 
	 * @return This Invoice as a String
	 */
	@Override
	public String toString() {
		final StringBuffer buffer = new StringBuffer();
		final DateFormat format = new SimpleDateFormat();

		buffer.append("Invoice ID: ");
		buffer.append(id);
		buffer.append("\nPatient: ");
		buffer.append(patient);
		buffer.append("\nDoctor: ");
		buffer.append(doctor);
		buffer.append("\nDate: ");
		buffer.append(format.format(date));
		buffer.append("\nItems:");

		for (InvoiceItem item : items) {
			buffer.append("\n\t");
			buffer.append(item);
		}

		return buffer.toString();
	}

	/**
	 * Serializes this object into a JSONObject.
	 * 
	 * @return The JSON representation of this class
	 */
	@Override
	public JSONObject toJSONObject() {
		final JSONObject object = new JSONObject();

		try {
			object.put("id", id);
			object.put("patient", patient.toJSONObject());
			object.put("doctor", doctor.toJSONObject());
			object.put("date", date.getTime());
			object.put("total", getTotal());

			final JSONArray array = new JSONArray();

			for (InvoiceItem item : items) {
				array.put(item.toJSONObject());
			}

			object.put("items", array);
		} catch (JSONException e) {
			LOG.log(Level.SEVERE, "Unexpected exception", e);
		}

		return object;
	}

	/**
	 * Returns a string that uniquely identifies an instance of this class.
	 * 
	 * @return This instance's unique identifier
	 */
	@Override
	public String getUniqueIdentifier() {
		return "" + id;
	}

	/**
	 * Compares this Invoice to another. When comparing Invoices, the Invoice
	 * with the smaller ID is considered less.
	 * 
	 * @param invoice
	 *            The Invoice to compare to
	 * @return -1 if this Invoice is less than the other, 1 if it is greater,
	 *         and 0 otherwise
	 */
	@Override
	public int compareTo(Invoice invoice) {
		return new Integer(id).compareTo(invoice.id);
	}

	/**
	 * Determines if this Invoice is equal to some other Object.
	 * 
	 * @return true if this object is equal to another, or false otherwise
	 * @see java.lang.Object#equals(Object)
	 */
	@Override
	public boolean equals(Object other) {
		return other instanceof Invoice && compareTo((Invoice) other) == 0;
	}

	/**
	 * Returns an integer representation of this object.
	 * 
	 * @return An integer representing this object
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode() {
		return id;
	}
}
